/**
 * map 方法只能返回一个参数，这个方法是用来返回多个的。
 * demo
   const list = [{ a: 'a1', b: 'b1' }, { a: 'a2', b: 'b2' }, ]
   const [a, b] = list.maps(_ => _.a, _ => _.b)
   console.log(a, b)
 */
Array.prototype.maps = function (...args) {
    // 初始化空数组，这是一个二维数组，长度与参数一致
    let ary = args.map(_ => [])

    // 开始遍历自身
    this.forEach((val, index, array) => {
        // 依次执行 fn
        for (let i = 0, len = args.length; i < len; i++) {
            // 获取当前函数
            const fn = args[i]
            // 调用函数，如果不是函数的话，就直接返回本身
            const result = isFunction(fn) ? fn(val, index, array) : fn
            // 插入第n个里边
            ary[i].push(result)
        }
    })

    // 返回最终结果
    return ary
}

/**
 * 获取数组最后一位
 */
Array.prototype.last = function () {
    return this[this.length - 1]
}

/**
 * 获取数组第一位
 */
Array.prototype.first = function () {
    return this[0]
}

/**
 * 最简单且最安全的方法显示一个值，举个例子: 
 * var obj = {a: 123 }
   maybe(_=> obj.a, 0); // 123
   maybe(_=> obj.b, 0); // 0
   maybe(_=> obj.a.b.s.w.holy.shit.fuck.god, 0); // 0
 */
export const maybe = (fn, n = '') => {
    try {
        const result = fn()

        return (result && result === result && result !== 'NaN' && result !== 'undefined' && result !== 'Invalid date') ? result : n
    } catch (err) {
        return n
    }
}

/**
 * console.log(formatDate(1592220951 * 1000, 'yyyy-MM-dd hh:mm:ss')) *
 * 时间格式化 
 * @param  {[type]} date 需要转换的时间
 * @param  {[type]} fmt  转换后的时间格式   如'yyyy-MM-dd hh:mm:ss'
 * @return Promise
 */
export function formatDate(time, fmt) {
    let date = new Date(time);
    if (/(y+)/.test(fmt)) {
        fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
    }
    let o = {
        'M+': date.getMonth() + 1,
        'd+': date.getDate(),
        'h+': date.getHours(),
        'm+': date.getMinutes(),
        's+': date.getSeconds()
    };
    for (let k in o) {
        if (new RegExp(`(${k})`).test(fmt)) {
            let str = o[k] + '';
            fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : ('00' + str).substr(str.length));
        }
    }
    return fmt;
};

/**
 * 获取天数差 
 * Example   getDaysDiffBetweenDates(new Date("2016-12-01"), new Date("2021-01-01")); // 9
 * @param {*} dateInitial 
 * @param {*} dateFinal 
 */
export const getDaysDiffBetweenDates = (dateInitial, dateFinal) => (dateFinal - dateInitial) / (1000 * 3600 * 24);

/**
 * 获取两个日期之间月数差
 * Example console.log('abc', getMonthBetween('2016-10', '2021-01'))  52;
 * @param {*} start 
 * @param {*} end 
 */
export function getMonthBetween(start, end) {
    let result = [];
    let s = start.split("-");
    let e = end.split("-");
    let min = new Date();
    let max = new Date();
    min.setFullYear(s[0], s[1]);
    max.setFullYear(e[0], e[1]);
    let curr = min;
    while (curr <= max) {
        let month = curr.getMonth();
        let str = curr.getFullYear() + "-" + (month);
        let s = curr.getFullYear() + "-0";
        if (str == s) {
            str = curr.getFullYear() + "-12";
        }
        result.push(str);
        curr.setMonth(month + 1);
    }
    return result.length ? result.length : 0;
}

/**
 * 字符串定义几行显示
 * @param {*} name 字符串
 * @param {*} num  每行显示的字数
 */
export const lineFeedStr = (name, num) => {
    let newParamsName = ""; // 最终拼接成的字符串
    let paramsNameNumber = name.length; // 实际标签的个数
    let provideNumber = num; // 每行能显示的字的个数
    let rowNumber = Math.ceil(paramsNameNumber / provideNumber); // 换行的话，需要显示几行，向上取整
    /**
     * 判断标签的个数是否大于规定的个数， 如果大于，则进行换行处理 如果不大于，即等于或小于，就返回原标签
     */
    // 条件等同于rowNumber>1
    if (paramsNameNumber > provideNumber) {
        /** 循环每一行,p表示行 */
        for (let p = 0; p < rowNumber; p++) {
            let tempStr = ""; // 表示每一次截取的字符串
            let start = p * provideNumber; // 开始截取的位置
            let end = start + provideNumber; // 结束截取的位置
            // 此处特殊处理最后一行的索引值
            if (p == rowNumber - 1) {
                // 最后一次不换行
                tempStr = name.substring(start, paramsNameNumber);
            } else {
                // 每一次拼接字符串并换行
                tempStr = name.substring(start, end) + "\n";
            }
            newParamsName += tempStr; // 最终拼成的字符串
        }

    } else {
        // 将旧标签的值赋给新标签
        newParamsName = name;
    }
    //将最终的字符串返回
    return newParamsName
}

/**
 * 以大带小
 *  const data = drive(newV.data, (a, b) => +a.amount - +b.amount)
 *  this.render(data)
 */
export const drive = (ary, fn) => {
    // 数组长度
    const len = ary.length
    // 偶数长度
    const isEven = len % 2 === 0
    // 从大到小排序
    const _ary = ary.sort(fn)
    // 循环的次数
    const time = isEven ? len / 2 : Math.floor(len / 2) + 1
    // 返回新的数组
    return [...Array(time)].reduce((previousValue, currentValue, index) => {
        // 是否循环的最后一次
        const isLastTime = index === time - 1
        // 如果是奇数的，且最后一次的情况下
        if (!isEven && isLastTime) {
            // 没人可带，带我自己就够了
            return [...previousValue, _ary[index]]
            // 默认都带一个小弟
        } else {
            // 带上小弟
            return [...previousValue, _ary[index], _ary[len - 1 - index]]
        }
    }, [])
}

/**
 * 示例:
 * 
 * clickOutSide('.u-pointCard', cancel => {
 *     // 关闭弹窗
 *     this.points_html = ''
 *     // 取消事件本身
 *     cancel()
 * })
 */
export const clickOutSide = (className, fn) => {
    const _clickOutSide = e => {
        const el = document.querySelector(className)
        // 如果点击的不是指定的元素，那么执行回调，并且取消这个事件本身
        if (el && el.contains(e.target) === false) {
            // 执行函数，并且注入取消的回调
            fn(() => document.removeEventListener('mouseup', _clickOutSide))
        }
    }
    document.addEventListener('mouseup', _clickOutSide)
}

// 简单对比双方是否相等，如果不等就赋值
// 为了解决 Vue.watch 监听引用类型总是更新的问题：无论你是否数据是否一致，由于引用类型的地址是不一致的，所以总会触发更新，这很浪费性能。
export const diffSet = function (leftPath, rightValue, key) {
    // news: left 可能传入一个路径，而不是简单的一个参数了。
    const leftValue = parsePath(this, leftPath)

    // 分别对 left/right 进行序列化
    const _left = JSON.stringify(leftValue),
        _right = JSON.stringify(rightValue)

    // 如果默认是空数据结构，那想都不用想就赋值吧
    const isEmpty = _left === '{}' || _left === '[]' || _left === 'null' || !_left

    // 内容不同才赋值
    if (isEmpty || _left != _right) {
        // 深度设置
        $deepSet(this, leftPath, rightValue)
    }
}
/**
 * （推荐）深度设置 ...
 *
 * var a = {}
 * deepSet(a, 'a.b.c.d.e.f.g.h.i.j.k', 123)
 */
const $deepSet = (obj, path, v) => {
    // （重要）保存引用
    let o = obj

    // 分解路径
    const p = path.split('.')

    // 取出最后一位
    const last = p.pop()

    // 不断轮询路径
    while (p.length) {
        // 从左往右取出路径
        const k = p.shift()

        // ⚠️ 如果不存在则定义该对象
        if (!o[k]) o[k] = {}

        // 获取当前路径的值
        o = o[k]
    }

    // o[last] = typeof v === 'function' ? v() : v

    // fixbug: vue set
    Vue.set(o, last, typeof v === 'function' ? v() : v)

    // （重点）返回被串改的数组
    return obj
}

/**
 * 递归查找
 * 示例 this.dataArr = searchItem(this.menu.menuTree.children, value, 'department', 'children')
 * @param {*} list 要匹配的数据
 * @param {*} id   要匹配的值
 * @param {*} idKey 要匹配的字段
 * @param {*} childrenKey 要匹配的子级
 */
const recursionList = (list, id, idKey, childrenKey) => {
    for (let i = 0; i < list.length; i++) {
        const element = list[i];

        if (element[idKey] === id) {
            return element
        } else {
            const newList = element[childrenKey] || []
            const res = recursionList(newList, id, idKey, childrenKey)
            if (res) return res
        }
    }
}

const searchItem = (dataArr, id, idKey, childrenKey) => {
    const resultArr = []
    for (let j = 0; j < dataArr.length; j++) {
        const jElement = dataArr[j];

        const jList = [jElement]

        const jResult = recursionList(jList, id, idKey, childrenKey)
        if (jResult) {
            resultArr.push(jElement)
        }
    }

    return resultArr
}

/**
 * 一维数组返回树状数组
 * @param {*} list 匹配的数组
 * @param {*} props id= 当前id, pid=父id, children=子级数组
 * @returns 
 */
 export const arrayToTree = (list = [], props = {id: 'ID', pid: 'PID', children: 'child'}) => {
    let tree = [];
    let map = {};

    let listLength = list.length;
    for (let i = 0; i < listLength; i++) {
        let node = list[i];
        let nodeId = node[props.id];
        map[nodeId] = node;
    }

    for (let i = 0; i < listLength; i++) {
        let node = list[i];
        let nodePid = node[props.pid];
        let parentNode = map[nodePid];
        if (parentNode) {
            parentNode[props.children] = parentNode[props.children] || [];
            parentNode[props.children].push(node)
        } else {
            tree.push(node)
        }
    }

    return tree
}